﻿#region COPYRIGHT© 2009-2012 Phillip Clark. All rights reserved.
// For licensing information see License.txt (MIT style licensing).
#endregion

using FlitBit.Parse;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Globalization;

namespace FlitBit.Parse.Tests
{
	/// <summary>
	/// Summary description for CharMatchTests
	/// </summary>
	[TestClass]
	public class CharMatchTests
	{
		public TestContext TestContext { get; set; }
		[TestMethod]
		public void Exact_BasicFunctionality()
		{
			var sin = "aaaaaaaaaabc";
			var scursor = new Cursor(0);

			var a = CharMatch.Exact('a');
			Assert.IsTrue(a.IsCaseSensitive);
			Assert.IsFalse(a.IsVariable);
			Assert.AreEqual(1, a.SpecificChars.Length);
			Assert.AreEqual('a', a.SpecificChars[0]);

			while (a.At(sin, scursor)) // test input at cursor
			{
				// advance if match at cursor -- should always succeed due to the prior check
				Assert.IsTrue(a.TryAt(sin, ref scursor));
			}
			Assert.AreEqual(10, scursor.Offset);
		}

		[TestMethod]
		public void CaseInsensitive_BasicFunctionality()
		{
			var input = "aaAAaaaAaabc".ToCharArray();
			var cursor = new Cursor(0);

			var a = CharMatch.CaseInsensitive('a');
			Assert.IsFalse(a.IsCaseSensitive);
			Assert.IsFalse(a.IsVariable);
			Assert.AreEqual(2, a.SpecificChars.Length);
			Assert.AreEqual('a', a.SpecificChars[0]);
			Assert.AreEqual('A', a.SpecificChars[1]);

			while (a.At(input, cursor)) // test input at cursor
			{
				// advance if match at cursor -- should always succeed due to the prior check
				Assert.IsTrue(a.TryAt(input, ref cursor));
			}
			Assert.AreEqual(10, cursor.Offset, "Should fail at the offset of the 'b' leaving 'bc' in the input.");
		}

		[TestMethod]
		public void TestEquality()
		{
			var cin = new char[] { 'a', 'b', 'c' };
			var sin = "abc";
			var ccursor = new Cursor(0);
			var scursor = new Cursor(0);

			var a = CharMatch.Exact('a');
			Assert.IsTrue(a.IsCaseSensitive);
			Assert.IsFalse(a.IsVariable);
			Assert.AreEqual(1, a.SpecificChars.Length);
			Assert.AreEqual('a', a.SpecificChars[0]);
			Assert.IsTrue(a.At(cin, ccursor));
			Assert.IsTrue(a.At(sin, scursor));
			Assert.IsTrue(a.TryAt(cin, ref ccursor));
			Assert.IsTrue(a.TryAt(sin, ref scursor));
			Assert.IsFalse(a.At(cin, ccursor));
			Assert.IsFalse(a.At(sin, scursor));

			var b = CharMatch.Exact('b');
			Assert.IsTrue(b.IsCaseSensitive);
			Assert.IsFalse(b.IsVariable);
			Assert.AreEqual(1, b.SpecificChars.Length);
			Assert.AreEqual('b', b.SpecificChars[0]);
			Assert.IsTrue(b.At(cin, ccursor));
			Assert.IsTrue(b.At(sin, scursor));
			Assert.IsTrue(b.TryAt(cin, ref ccursor));
			Assert.IsTrue(b.TryAt(sin, ref scursor));

			Assert.IsFalse(a == b);
			Assert.IsTrue(a != b);
			Assert.AreEqual(CharMatch.Exact('a'), a);
			Assert.AreEqual(CharMatch.Exact('b'), b);
			Assert.AreNotEqual(CharMatch.CaseInsensitive('a'), a);
			Assert.AreNotEqual(CharMatch.CaseInsensitive('b'), b);
		}

		[TestMethod]
		public void MatchExactChar()
		{
			var m = CharMatch.Exact('a');
			var input = @"we're going to match an exact character";
			//                            ^16  ^21  ^26   ^ ^32,34                  
			Cursor c = new Cursor(0);
			AdvanceWithNoMatch(16, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)17, c);

			AdvanceWithNoMatch(21, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)22, c);

			AdvanceWithNoMatch(26, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)27, c);

			AdvanceWithNoMatch(32, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)33, c);

			AdvanceWithNoMatch(34, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)35, c);

			AdvanceWithNoMatch(input.Length, m, ref c, input);

			Assert.AreEqual(CharMatch.Exact('a'), m);
		}

		[TestMethod]
		public void MatchCaseInsensitiveChar()
		{
			var m = CharMatch.CaseInsensitive('a');
			var input = @"we're going to match a cAse insensitive chAracter";
			//                            ^16  ^21^24               ^ ^42,44                  
			Cursor c = new Cursor(0);
			AdvanceWithNoMatch(16, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)17, c);

			AdvanceWithNoMatch(21, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)22, c);

			AdvanceWithNoMatch(24, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)25, c);

			AdvanceWithNoMatch(42, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)43, c);

			AdvanceWithNoMatch(44, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)45, c);

			AdvanceWithNoMatch(input.Length, m, ref c, input);

			Assert.AreEqual(CharMatch.CaseInsensitive('a'), m);
		}

		[TestMethod]
		public void MatchUnicodeCategory()
		{
			var m = CharMatch.UnicodeCategory(UnicodeCategory.DecimalDigitNumber);
			var input = @"1.) we're going to match decimal digits, 2.) there's no number 3";
			//            ^0                                       ^41                   ^63
			Cursor c = new Cursor(0);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)1, c);

			AdvanceWithNoMatch(41, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)42, c);

			AdvanceWithNoMatch(63, m, ref c, input);
			Assert.IsTrue(m.TryAt(input, ref c));
			Assert.AreEqual((Cursor)64, c);

			Assert.AreEqual(CharMatch.UnicodeCategory(UnicodeCategory.DecimalDigitNumber), m);
		}

		[TestMethod]
		public void MatchAnyCharExcept()
		{
			var m = CharMatch.AnyExcept('a');
			var input = @"we're going to match an exact character";
			//                            ^16  ^21  ^26   ^ ^32,34                  
			Cursor c = new Cursor(0);
			AdvanceWithMatch(16, m, ref c, input);
			Assert.IsFalse(m.At(input, c));
			c++;

			AdvanceWithMatch(21, m, ref c, input);
			Assert.IsFalse(m.At(input, c));
			c++;

			Assert.AreEqual(CharMatch.AnyExcept('a'), m);
		}

		private void AdvanceWithMatch(int upperLimit, CharMatch rule, ref Cursor c, string input)
		{
			while (c < upperLimit)
			{
				Assert.IsTrue(rule.TryAt(input, ref c));
			}
		}

		private void AdvanceWithNoMatch(int upperLimit, CharMatch rule, ref Cursor c, string input)
		{
			for (; c < upperLimit; c++)
			{
				Cursor cc = c;
				Assert.IsFalse(rule.At(input, c));
				Assert.AreEqual(cc, c, "cursor should not advance unless there is a match");
			}
		}
	}
}
